package language;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

public class LMagnitude extends LType implements Comparable<LMagnitude> {

    private final BigDecimal value;
    private final boolean isChar;

    public LMagnitude(BigDecimal value, boolean isChar) {
        this.value = value;
        this.isChar = isChar;
    }

    public LMagnitude(LMagnitude mag, boolean isChar) {
        this(mag.value, isChar);
    }

    public LMagnitude abs() {
        return new LMagnitude(value.abs(), isChar);
    }

    public LMagnitude add(LMagnitude that) {
        return new LMagnitude(this.value.add(that.value), isChar);
    }

    public LMagnitude subtract(LMagnitude that) {
        return new LMagnitude(this.value.subtract(that.value), isChar);
    }

    public LMagnitude multiply(LMagnitude that) {
        return new LMagnitude(this.value.multiply(that.value), isChar);
    }

    public LMagnitude divide(LMagnitude that) {
        return new LMagnitude(this.value.divide(that.value, value.scale() - that.value.scale() + 15, RoundingMode.DOWN), isChar);
    }

    public LMagnitude modulo(LMagnitude that) {
        BigDecimal dec = this.value.remainder(that.value);
        if (dec.compareTo(BigDecimal.ZERO) < 0 && !dec.remainder(BigDecimal.ONE).equals(BigDecimal.ZERO)) {
            dec = dec.subtract(BigDecimal.ONE);
        }
        return new LMagnitude(dec, isChar);
    }

    public LMagnitude power(LMagnitude that) {
        return new LMagnitude(new BigDecimal(Math.pow(this.toDouble(), that.toDouble())), isChar);
    }

    public LMagnitude log(LMagnitude base) {
        return new LMagnitude(new BigDecimal(Math.log(toDouble()) / Math.log(base.toDouble())), isChar);
    }

    public LMagnitude bitwiseAnd(LMagnitude that) {
        return new LMagnitude(new BigDecimal(this.toLong() & that.toLong()), isChar);
    }

    public LMagnitude bitwiseOr(LMagnitude that) {
        return new LMagnitude(new BigDecimal(this.toLong() | that.toLong()), isChar);
    }

    public LMagnitude bitwiseXor(LMagnitude that) {
        return new LMagnitude(new BigDecimal(this.toLong() ^ that.toLong()), isChar);
    }

    public static LArray<? extends LMagnitude> fromBase10(LMagnitude newBase, LMagnitude number) {
        long base = newBase.toLong();
        long num = number.toLong();
        List<LMagnitude> result = new ArrayList();
        while (num > base) {
            result.add(new LNumber(num % base));
            num /= base;
        }
        result.add(new LNumber(num));
        List<LMagnitude> finalResult = new ArrayList();
        for (int i = result.size() - 1; i >= 0; i--) {
            finalResult.add(result.get(i));
        }
        return LArray.of(finalResult);
    }

    public static LMagnitude toBase10(LMagnitude oldBase, LArray<? extends LMagnitude> number) {
        long base = oldBase.toLong();
        long num = 0;
        for (LMagnitude mag : number) {
            num += mag.toLong();
            num *= base;
        }
        num /= base;
        return new LNumber(num);
    }

    public static LArray<? extends LMagnitude> baseConv(LMagnitude oldBase, LMagnitude newBase, LArray<? extends LMagnitude> number) {
        return LMagnitude.fromBase10(newBase, toBase10(oldBase, number));
    }

    @Override
    public LNumber asNumber() {
        return new LNumber(this);
    }

    @Override
    public LCharacter asCharacter() {
        return new LCharacter(toChar());
    }

    public int toInt() {
        return value.intValue();
    }

    public LMagnitude roundToZero() {
        return new LMagnitude(new BigDecimal(value.toBigInteger()), isChar);
    }

    public long toLong() {
        return value.longValue();
    }

    public char toChar() {
        return (char) value.intValue();
    }

    public double toDouble() {
        return value.doubleValue();
    }

    @Override
    public boolean toBoolean() {
        return !value.equals(BigDecimal.ZERO);
    }

    @Override
    public String toString() {
        if (isChar) {
            return String.valueOf(toChar());
        }
        if (value.compareTo(BigDecimal.ZERO) == 0) {
            return "0";
        }
        String str = value.toString();
        if (str.contains(".")) {
            if (str.contains("E")) {
                String suffix = str.replaceFirst(".*(?=E)", "");
                String temp = str.replaceFirst("E.*", "");
                temp = temp.replaceFirst("(\\.\\d*?)0*$", "$1");
                temp = temp.replaceFirst("\\.$", "");
                str = temp + suffix;
            } else {
                str = str.replaceFirst("(\\.\\d*?)0*$", "$1");
                str = str.replaceFirst("\\.$", "");
            }
        }
        return str;
    }

    @Override
    public String toDebugString() {
        if (isChar) {
            return "'" + toChar() + "'";
        }
        return toString();
    }

    @Override
    public int compareTo(LMagnitude that) {
        return this.value.compareTo(that.value);
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (!(o instanceof LMagnitude)) {
            return false;
        }
        LMagnitude that = (LMagnitude) o;
        return this.value.compareTo(that.value) == 0;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + value.hashCode();
        return hash;
    }

    public boolean isChar() {
        return isChar;
    }
}
